/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockContainerWidget.h"
#include "QxDockAutoHideContainer.h"
#include "QxDockAutoHideSideBar.h"
#include "QxDockAutoHideTab.h"
#include "QxDockAreaTitleBar.h"
#include "QxDockAreaWidget.h"
#include "QxDockFocusController.h"
#include "QxDockManager.h"
#include "QxDockOverlay.h"
#include "QxDockSplitter.h"
#include "QxDockWidget.h"
#include "QxDockWidgetTab.h"
#include "QxDockStateReader.h"
#include "QxDockFloatingContainer.h"

#include <QAbstractButton>
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QGridLayout>
#include <QLabel>
#include <QList>
#include <QMetaObject>
#include <QMetaType>
#include <QPointer>
#include <QTimer>
#include <QVariant>
#include <QXmlStreamWriter>

#include <functional>
#include <iostream>

#if QT_VERSION < 0x050900

inline char toHexLower(uint value)
{
    return "0123456789abcdef"[value & 0xF];
}

QByteArray qByteArrayToHex(const QByteArray &src, char separator)
{
    if (src.size() == 0)
        return QByteArray();

    const int length = separator ? (src.size() * 3 - 1) : (src.size() * 2);
    QByteArray hex(length, Qt::Uninitialized);
    char *hexData = hex.data();
    const uchar *data = reinterpret_cast<const uchar *>(src.data());
    for (int i = 0, o = 0; i < src.size(); ++i) {
        hexData[o++] = toHexLower(data[i] >> 4);
        hexData[o++] = toHexLower(data[i] & 0xf);

        if ((separator) && (o < length))
            hexData[o++] = separator;
    }
    return hex;
}
#endif

QX_BEGIN_NAMESPACE

static unsigned int zOrderCounter = 0;

enum eDropMode {
    DropModeIntoArea,        ///< drop widget into a dock area
    DropModeIntoContainer,   ///< drop into container
    DropModeInvalid          ///< invalid mode - do not drop
};

/**
 * Converts dock area ID to an index for array access
 */
static int areaIdToIndex(DockWidgetArea area)
{
    switch (area) {
    case LeftDockWidgetArea:
        return 0;
    case RightDockWidgetArea:
        return 1;
    case TopDockWidgetArea:
        return 2;
    case BottomDockWidgetArea:
        return 3;
    case CenterDockWidgetArea:
        return 4;
    default:
        return 4;
    }
}

/**
 * Helper function to ease insertion of dock area into splitter
 */
static void insertWidgetIntoSplitter(QSplitter *Splitter, QWidget *widget, bool Append)
{
    if (Append) {
        Splitter->addWidget(widget);
    } else {
        Splitter->insertWidget(0, widget);
    }
}

/**
 * Private data class of DockContainerWidget class (pimpl)
 */
class DockContainerWidgetPrivate
{
public:
    DockContainerWidget *_this;
    QPointer<DockManager> dockManager;
    unsigned int zOrderIndex = 0;
    QList<DockAreaWidget *> DockAreas;
    QList<DockAutoHideContainer *> AutoHideWidgets;
    QMap<SideBarLocation, DockAutoHideSideBar *> SideTabBarWidgets;
    QGridLayout *Layout = nullptr;
    QSplitter *RootSplitter = nullptr;
    bool isFloating = false;
    DockAreaWidget *LastAddedAreaCache[5];
    int VisibleDockAreaCount = -1;
    DockAreaWidget *TopLevelDockArea = nullptr;
    QTimer DelayedAutoHideTimer;
    DockAutoHideTab *DelayedAutoHideTab;
    bool DelayedAutoHideShow = false;

    /**
     * Private data constructor
     */
    DockContainerWidgetPrivate(DockContainerWidget *_public);

    /**
     * Adds dock widget to container and returns the dock area that contains
     * the inserted dock widget
     */
    DockAreaWidget *addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockwidget);

    /**
     * Adds dock widget to a existing DockWidgetArea
     */
    DockAreaWidget *addDockWidgetToDockArea(DockWidgetArea area, DockWidget *dockwidget,
                                             DockAreaWidget *TargetDockArea, int Index = -1);

    /**
     * Add dock area to this container
     */
    void addDockArea(DockAreaWidget *NewDockWidget, DockWidgetArea area = CenterDockWidgetArea);

    /**
     * Drop floating widget into container
     */
    void dropIntoContainer(DockFloatingContainer *FloatingWidget, DockWidgetArea area);

    /**
     * Drop floating widget into dock area
     */
    void dropIntoSection(DockFloatingContainer *FloatingWidget, DockAreaWidget *TargetArea, DockWidgetArea area);

    /**
     * Moves the dock widget or dock area given in Widget parameter to a
     * new dock widget area
     */
    void moveToNewSection(QWidget *Widget, DockAreaWidget *TargetArea, DockWidgetArea area);

    /**
     * Moves the dock widget or dock area given in Widget parameter to a
     * a dock area in container
     */
    void moveToContainer(QWidget *Widgett, DockWidgetArea area);

    /**
     * Creates a new tab for a widget dropped into the center of a section
     */
    void dropIntoCenterOfSection(DockFloatingContainer *FloatingWidget, DockAreaWidget *TargetArea);

    /**
     * Creates a new tab for a widget dropped into the center of a section
     */
    void moveIntoCenterOfSection(QWidget *Widget, DockAreaWidget *TargetArea);

    /**
     * Adds new dock areas to the internal dock area list
     */
    void addDockAreasToList(const QList<DockAreaWidget *> NewDockAreas);

    /**
     * Wrapper function for DockAreas append, that ensures that dock area signals
     * are properly connected to dock container slots
     */
    void appendDockAreas(const QList<DockAreaWidget *> NewDockAreas);

    /**
     * Save state of child nodes
     */
    void saveChildNodesState(QXmlStreamWriter &Stream, QWidget *Widget);

    /**
     * Save state of auto hide widgets
     */
    void saveAutoHideWidgetsState(QXmlStreamWriter &Stream);

    /**
     * Restore state of child nodes.
     * \param[in] Stream The data stream that contains the serialized state
     * \param[out] CreatedWidget The widget created from parsed data or 0 if
     * the parsed widget was an empty splitter
     * \param[in] Testing If Testing is true, only the stream data is
     * parsed without modifiying anything.
     */
    bool restoreChildNodes(DockStateReader &Stream, QWidget *&CreatedWidget, bool Testing);

    /**
     * Restores a splitter.
     * \see restoreChildNodes() for details
     */
    bool restoreSplitter(DockStateReader &Stream, QWidget *&CreatedWidget, bool Testing);

    /**
     * Restores a dock area.
     * \see restoreChildNodes() for details
     */
    bool restoreDockArea(DockStateReader &Stream, QWidget *&CreatedWidget, bool Testing);

    /**
     * Restores a auto hide side bar
     */
    bool restoreSideBar(DockStateReader &Stream, QWidget *&CreatedWidget, bool Testing);

    /**
     * Helper function for recursive dumping of layout
     */
    void dumpRecursive(int level, QWidget *widget);

    /**
     * Calculate the drop mode from the given target position
     */
    eDropMode getDropMode(const QPoint &TargetPos);

    /**
     * Initializes the visible dock area count variable if it is not initialized
     * yet
     */
    void initVisibleDockAreaCount()
    {
        if (VisibleDockAreaCount > -1) {
            return;
        }

        VisibleDockAreaCount = 0;
        for (auto dockArea : DockAreas) {
            VisibleDockAreaCount += dockArea->isHidden() ? 0 : 1;
        }
    }

    /**
     * Access function for the visible dock area counter
     */
    int &visibleDockAreaCount()
    {
        // Lazy initialisation - we initialize the VisibleDockAreaCount variable
        // on first use
        initVisibleDockAreaCount();
        return VisibleDockAreaCount;
    }

    /**
     * The visible dock area count changes, if dock areas are remove, added or
     * when its view is toggled
     */
    void onVisibleDockAreaCountChanged();

    void emitDockAreasRemoved()
    {
        onVisibleDockAreaCountChanged();
        Q_EMIT _this->dockAreasRemoved();
    }

    void emitDockAreasAdded()
    {
        onVisibleDockAreaCountChanged();
        Q_EMIT _this->dockAreasAdded();
    }

    /**
     * Helper function for creation of new splitter
     */
    DockSplitter *newSplitter(Qt::Orientation orientation, QWidget *parent = nullptr)
    {
        DockSplitter *s = new DockSplitter(orientation, parent);
        s->setOpaqueResize(DockManager::testConfigFlag(DockManager::OpaqueSplitterResize));
        s->setChildrenCollapsible(false);
        return s;
    }

    /**
     * Ensures equal distribution of the sizes of a splitter if an dock widget
     * is inserted from code
     */
    void adjustSplitterSizesOnInsertion(QSplitter *Splitter, qreal LastRatio = 1.0)
    {
        int AreaSize = (Splitter->orientation() == Qt::Horizontal) ? Splitter->width() : Splitter->height();
        auto SplitterSizes = Splitter->sizes();

        qreal TotRatio = SplitterSizes.size() - 1.0 + LastRatio;
        for (int i = 0; i < SplitterSizes.size() - 1; i++) {
            SplitterSizes[i] = AreaSize / TotRatio;
        }
        SplitterSizes.back() = AreaSize * LastRatio / TotRatio;
        Splitter->setSizes(SplitterSizes);
    }

    /**
     * This function forces the dock container widget to update handles of splitters
     * based if a central widget exists.
     */
    void updateSplitterHandles(QSplitter *splitter);

    /**
     * If no central widget exists, the widgets resize with the container.
     * If a central widget exists, the widgets surrounding the central widget
     * do not resize its height or width.
     */
    bool widgetResizesWithContainer(QWidget *widget);

    // private slots: ------------------------------------------------------------
    void onDockAreaViewToggled(bool Visible)
    {
        DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(_this->sender());
        VisibleDockAreaCount += Visible ? 1 : -1;
        onVisibleDockAreaCountChanged();
        Q_EMIT _this->dockAreaViewToggled(dockArea, Visible);
    }
};   // struct DockContainerWidgetPrivate

DockContainerWidgetPrivate::DockContainerWidgetPrivate(DockContainerWidget *_public) : _this(_public)
{
    std::fill(std::begin(LastAddedAreaCache), std::end(LastAddedAreaCache), nullptr);
    DelayedAutoHideTimer.setSingleShot(true);
    DelayedAutoHideTimer.setInterval(500);
    QObject::connect(&DelayedAutoHideTimer, &QTimer::timeout, [this]() {
        auto GlobalPos = DelayedAutoHideTab->mapToGlobal(QPoint(0, 0));
        qApp->sendEvent(DelayedAutoHideTab, new QMouseEvent(QEvent::MouseButtonPress, QPoint(0, 0), GlobalPos,
                                                            Qt::LeftButton, {Qt::LeftButton}, Qt::NoModifier));
    });
}

eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint &TargetPos)
{
    DockAreaWidget *dockArea = _this->dockAreaAt(TargetPos);
    auto dropArea = InvalidDockWidgetArea;
    auto ContainerDropArea = dockManager->containerOverlay()->dropAreaUnderCursor();

    if (dockArea) {
        auto dropOverlay = dockManager->dockAreaOverlay();
        dropOverlay->setAllowedAreas(dockArea->allowedAreas());
        dropArea = dropOverlay->showOverlay(dockArea);
        if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) {
            dropArea = InvalidDockWidgetArea;
        }

        if (dropArea != InvalidDockWidgetArea) {
            QX_DOCK_PRINT("Dock Area Drop Content: " << dropArea);
            return DropModeIntoArea;
        }
    }

    // mouse is over container
    if (InvalidDockWidgetArea == dropArea) {
        dropArea = ContainerDropArea;
        QX_DOCK_PRINT("Container Drop Content: " << dropArea);
        if (dropArea != InvalidDockWidgetArea) {
            return DropModeIntoContainer;
        }
    }

    return DropModeInvalid;
}

void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged()
{
    auto TopLevelDockArea = _this->topLevelDockArea();

    if (TopLevelDockArea) {
        this->TopLevelDockArea = TopLevelDockArea;
        TopLevelDockArea->updateTitleBarButtonVisibility(true);
    } else if (this->TopLevelDockArea) {
        this->TopLevelDockArea->updateTitleBarButtonVisibility(false);
        this->TopLevelDockArea = nullptr;
    }
}

void DockContainerWidgetPrivate::dropIntoContainer(DockFloatingContainer *FloatingWidget, DockWidgetArea area)
{
    auto InsertParam = internal::dockAreaInsertParameters(area);
    DockContainerWidget *FloatingDockContainer = FloatingWidget->dockContainer();
    auto NewDockAreas = FloatingDockContainer->findChildren<DockAreaWidget *>(QString(), Qt::FindChildrenRecursively);
    QSplitter *Splitter = RootSplitter;

    if (DockAreas.count() <= 1) {
        Splitter->setOrientation(InsertParam.orientation());
    } else if (Splitter->orientation() != InsertParam.orientation()) {
        QSplitter *NewSplitter = newSplitter(InsertParam.orientation());
        QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter);
        NewSplitter->addWidget(Splitter);
        updateSplitterHandles(NewSplitter);
        Splitter = NewSplitter;
        delete li;
    }

    // Now we can insert the floating widget content into this container
    auto FloatingSplitter = FloatingDockContainer->rootSplitter();
    if (FloatingSplitter->count() == 1) {
        insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append());
        updateSplitterHandles(Splitter);
    } else if (FloatingSplitter->orientation() == InsertParam.orientation()) {
        int InsertIndex = InsertParam.append() ? Splitter->count() : 0;
        while (FloatingSplitter->count()) {
            Splitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0));
            updateSplitterHandles(Splitter);
        }
    } else {
        insertWidgetIntoSplitter(Splitter, FloatingSplitter, InsertParam.append());
    }

    RootSplitter = Splitter;
    addDockAreasToList(NewDockAreas);

    // If we dropped the floating widget into the main dock container that does
    // not contain any dock widgets, then splitter is invisible and we need to
    // show it to display the docked widgets
    if (!Splitter->isVisible()) {
        Splitter->show();
    }
    _this->dumpLayout();
}

void DockContainerWidgetPrivate::dropIntoCenterOfSection(DockFloatingContainer *FloatingWidget,
                                                         DockAreaWidget *TargetArea)
{
    DockContainerWidget *FloatingContainer = FloatingWidget->dockContainer();
    auto NewDockWidgets = FloatingContainer->dockWidgets();
    auto TopLevelDockArea = FloatingContainer->topLevelDockArea();
    int NewCurrentIndex = -1;

    // If the floating widget contains only one single dock are, then the
    // current dock widget of the dock area will also be the future current
    // dock widget in the drop area.
    if (TopLevelDockArea) {
        NewCurrentIndex = TopLevelDockArea->currentIndex();
    }

    for (int i = 0; i < NewDockWidgets.count(); ++i) {
        DockWidget *dockWidget = NewDockWidgets[i];
        TargetArea->insertDockWidget(i, dockWidget, false);
        // If the floating widget contains multiple visible dock areas, then we
        // simply pick the first visible open dock widget and make it
        // the current one.
        if (NewCurrentIndex < 0 && !dockWidget->isClosed()) {
            NewCurrentIndex = i;
        }
    }
    TargetArea->setCurrentIndex(NewCurrentIndex);
    TargetArea->updateTitleBarVisibility();
    return;
}

void DockContainerWidgetPrivate::dropIntoSection(DockFloatingContainer *FloatingWidget, DockAreaWidget *TargetArea,
                                                 DockWidgetArea area)
{
    // Dropping into center means all dock widgets in the dropped floating
    // widget will become tabs of the drop area
    if (CenterDockWidgetArea == area) {
        dropIntoCenterOfSection(FloatingWidget, TargetArea);
        return;
    }

    DockContainerWidget *FloatingContainer = FloatingWidget->dockContainer();
    auto InsertParam = internal::dockAreaInsertParameters(area);
    auto NewDockAreas = FloatingContainer->findChildren<DockAreaWidget *>(QString(), Qt::FindChildrenRecursively);
    QSplitter *TargetAreaSplitter = internal::findParent<QSplitter *>(TargetArea);

    if (!TargetAreaSplitter) {
        QSplitter *Splitter = newSplitter(InsertParam.orientation());
        Layout->replaceWidget(TargetArea, Splitter);
        Splitter->addWidget(TargetArea);
        updateSplitterHandles(Splitter);
        TargetAreaSplitter = Splitter;
    }
    int AreaIndex = TargetAreaSplitter->indexOf(TargetArea);
    auto FloatingSplitter = FloatingContainer->rootSplitter();
    if (TargetAreaSplitter->orientation() == InsertParam.orientation()) {
        auto Sizes = TargetAreaSplitter->sizes();
        int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height();
        bool AdjustSplitterSizes = true;
        if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) {
            TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), FloatingSplitter);
            updateSplitterHandles(TargetAreaSplitter);
        } else {
            AdjustSplitterSizes = (FloatingSplitter->count() == 1);
            int InsertIndex = AreaIndex + InsertParam.insertOffset();
            while (FloatingSplitter->count()) {
                TargetAreaSplitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0));
                updateSplitterHandles(TargetAreaSplitter);
            }
        }

        if (AdjustSplitterSizes) {
            int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2;
            Sizes[AreaIndex] = Size;
            Sizes.insert(AreaIndex, Size);
            TargetAreaSplitter->setSizes(Sizes);
        }
    } else {
        QSplitter *NewSplitter = newSplitter(InsertParam.orientation());
        int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height();
        bool AdjustSplitterSizes = true;
        if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) {
            NewSplitter->addWidget(FloatingSplitter);
            updateSplitterHandles(NewSplitter);
        } else {
            AdjustSplitterSizes = (FloatingSplitter->count() == 1);
            while (FloatingSplitter->count()) {
                NewSplitter->addWidget(FloatingSplitter->widget(0));
                updateSplitterHandles(NewSplitter);
            }
        }

        // Save the sizes before insertion and restore it later to prevent
        // shrinking of existing area
        auto Sizes = TargetAreaSplitter->sizes();
        insertWidgetIntoSplitter(NewSplitter, TargetArea, !InsertParam.append());
        updateSplitterHandles(NewSplitter);
        if (AdjustSplitterSizes) {
            int Size = TargetAreaSize / 2;
            NewSplitter->setSizes({Size, Size});
        }
        TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter);
        TargetAreaSplitter->setSizes(Sizes);
        updateSplitterHandles(TargetAreaSplitter);
    }

    addDockAreasToList(NewDockAreas);
    _this->dumpLayout();
}

void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget *Widget, DockAreaWidget *TargetArea)
{
    auto DroppedDockWidget = qobject_cast<DockWidget *>(Widget);
    auto DroppedArea = qobject_cast<DockAreaWidget *>(Widget);

    if (DroppedDockWidget) {
        DockAreaWidget *OldDockArea = DroppedDockWidget->dockAreaWidget();
        if (OldDockArea == TargetArea) {
            return;
        }

        if (OldDockArea) {
            OldDockArea->removeDockWidget(DroppedDockWidget);
        }
        TargetArea->insertDockWidget(0, DroppedDockWidget, true);
    } else {
        QList<DockWidget *> NewDockWidgets = DroppedArea->dockWidgets();
        int NewCurrentIndex = DroppedArea->currentIndex();
        for (int i = 0; i < NewDockWidgets.count(); ++i) {
            DockWidget *dockWidget = NewDockWidgets[i];
            TargetArea->insertDockWidget(i, dockWidget, false);
        }
        TargetArea->setCurrentIndex(NewCurrentIndex);
        DroppedArea->dockContainer()->removeDockArea(DroppedArea);
        DroppedArea->deleteLater();
    }

    TargetArea->updateTitleBarVisibility();
    return;
}

void DockContainerWidgetPrivate::moveToNewSection(QWidget *Widget, DockAreaWidget *TargetArea, DockWidgetArea area)
{
    // Dropping into center means all dock widgets in the dropped floating
    // widget will become tabs of the drop area
    if (CenterDockWidgetArea == area) {
        moveIntoCenterOfSection(Widget, TargetArea);
        return;
    }

    DockWidget *DroppedDockWidget = qobject_cast<DockWidget *>(Widget);
    DockAreaWidget *DroppedDockArea = qobject_cast<DockAreaWidget *>(Widget);
    DockAreaWidget *NewDockArea;
    if (DroppedDockWidget) {
        NewDockArea = new DockAreaWidget(dockManager, _this);
        DockAreaWidget *OldDockArea = DroppedDockWidget->dockAreaWidget();
        if (OldDockArea) {
            OldDockArea->removeDockWidget(DroppedDockWidget);
        }
        NewDockArea->addDockWidget(DroppedDockWidget);
    } else {
        DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea);
        NewDockArea = DroppedDockArea;
    }

    auto InsertParam = internal::dockAreaInsertParameters(area);
    QSplitter *TargetAreaSplitter = internal::findParent<QSplitter *>(TargetArea);
    int AreaIndex = TargetAreaSplitter->indexOf(TargetArea);
    auto Sizes = TargetAreaSplitter->sizes();
    if (TargetAreaSplitter->orientation() == InsertParam.orientation()) {
        int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height();
        TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), NewDockArea);
        updateSplitterHandles(TargetAreaSplitter);
        int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2;
        Sizes[AreaIndex] = Size;
        Sizes.insert(AreaIndex, Size);
    } else {
        int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height();
        QSplitter *NewSplitter = newSplitter(InsertParam.orientation());
        NewSplitter->addWidget(TargetArea);
        insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append());
        updateSplitterHandles(NewSplitter);
        int Size = TargetAreaSize / 2;
        NewSplitter->setSizes({Size, Size});
        TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter);
        updateSplitterHandles(TargetAreaSplitter);
    }
    TargetAreaSplitter->setSizes(Sizes);

    addDockAreasToList({NewDockArea});
}

void DockContainerWidgetPrivate::updateSplitterHandles(QSplitter *splitter)
{
    if (!dockManager->centralWidget() || !splitter) {
        return;
    }

    for (int i = 0; i < splitter->count(); ++i) {
        splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0);
    }
}

bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget *widget)
{
    if (!dockManager->centralWidget()) {
        return true;
    }

    auto Area = qobject_cast<DockAreaWidget *>(widget);
    if (Area) {
        return Area->isCentralWidgetArea();
    }

    auto innerSplitter = qobject_cast<DockSplitter *>(widget);
    if (innerSplitter) {
        return innerSplitter->isResizingWithContainer();
    }

    return false;
}

void DockContainerWidgetPrivate::moveToContainer(QWidget *Widget, DockWidgetArea area)
{
    DockWidget *DroppedDockWidget = qobject_cast<DockWidget *>(Widget);
    DockAreaWidget *DroppedDockArea = qobject_cast<DockAreaWidget *>(Widget);
    DockAreaWidget *NewDockArea;

    if (DroppedDockWidget) {
        NewDockArea = new DockAreaWidget(dockManager, _this);
        DockAreaWidget *OldDockArea = DroppedDockWidget->dockAreaWidget();
        if (OldDockArea) {
            OldDockArea->removeDockWidget(DroppedDockWidget);
        }
        NewDockArea->addDockWidget(DroppedDockWidget);
    } else {
        // We check, if we insert the dropped widget into the same place that
        // it already has and do nothing, if it is the same place. It would
        // also work without this check, but it looks nicer with the check
        // because there will be no layout updates
        auto Splitter = internal::findParent<DockSplitter *>(DroppedDockArea);
        auto InsertParam = internal::dockAreaInsertParameters(area);
        if (Splitter == RootSplitter && InsertParam.orientation() == Splitter->orientation()) {
            if (InsertParam.append() && Splitter->lastWidget() == DroppedDockArea) {
                return;
            } else if (!InsertParam.append() && Splitter->firstWidget() == DroppedDockArea) {
                return;
            }
        }
        DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea);
        NewDockArea = DroppedDockArea;
    }

    addDockArea(NewDockArea, area);
    LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea;
}

void DockContainerWidgetPrivate::addDockAreasToList(const QList<DockAreaWidget *> NewDockAreas)
{
    int CountBefore = DockAreas.count();
    int NewAreaCount = NewDockAreas.count();
    appendDockAreas(NewDockAreas);
    // If the user dropped a floating widget that contains only one single
    // visible dock area, then its title bar button TitleBarButtonUndock is
    // likely hidden. We need to ensure, that it is visible
    for (auto dockArea : NewDockAreas) {
        dockArea->titleBarButton(TitleBarButtonClose)->setVisible(true);
        dockArea->titleBarButton(TitleBarButtonAutoHide)->setVisible(true);
    }

    // We need to ensure, that the dock area title bar is visible. The title bar
    // is invisible, if the dock are is a single dock area in a floating widget.
    if (1 == CountBefore) {
        DockAreas.at(0)->updateTitleBarVisibility();
    }

    if (1 == NewAreaCount) {
        DockAreas.last()->updateTitleBarVisibility();
    }

    emitDockAreasAdded();
}

void DockContainerWidgetPrivate::appendDockAreas(const QList<DockAreaWidget *> NewDockAreas)
{
    DockAreas.append(NewDockAreas);
    for (auto dockArea : NewDockAreas) {
        QObject::connect(dockArea, &DockAreaWidget::viewToggled, _this,
                         std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, this, std::placeholders::_1));
    }
}

void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &s, QWidget *Widget)
{
    QSplitter *Splitter = qobject_cast<QSplitter *>(Widget);
    if (Splitter) {
        s.writeStartElement("Splitter");
        s.writeAttribute("Orientation", (Splitter->orientation() == Qt::Horizontal) ? "|" : "-");
        s.writeAttribute("Count", QString::number(Splitter->count()));
        QX_DOCK_PRINT("NodeSplitter orient: " << Splitter->orientation() << " WidgetCont: " << Splitter->count());
        for (int i = 0; i < Splitter->count(); ++i) {
            saveChildNodesState(s, Splitter->widget(i));
        }

        s.writeStartElement("Sizes");
        for (auto Size : Splitter->sizes()) {
            s.writeCharacters(QString::number(Size) + " ");
        }
        s.writeEndElement();
        s.writeEndElement();
    } else {
        DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(Widget);
        if (dockArea) {
            dockArea->saveState(s);
        }
    }
}

void DockContainerWidgetPrivate::saveAutoHideWidgetsState(QXmlStreamWriter &s)
{
    for (const auto sideTabBar : SideTabBarWidgets.values()) {
        if (!sideTabBar->tabCount()) {
            continue;
        }

        sideTabBar->saveState(s);
    }
}

bool DockContainerWidgetPrivate::restoreSplitter(DockStateReader &s, QWidget *&CreatedWidget, bool Testing)
{
    bool Ok;
    QString OrientationStr = s.attributes().value("Orientation").toString();

    // Check if the orientation string is right
    if (!OrientationStr.startsWith("|") && !OrientationStr.startsWith("-")) {
        return false;
    }

    // The "|" shall indicate a vertical splitter handle which in turn means
    // a Horizontal orientation of the splitter layout.
    bool HorizontalSplitter = OrientationStr.startsWith("|");
    // In version 0 we had a small bug. The "|" indicated a vertical orientation,
    // but this is wrong, because only the splitter handle is vertical, the
    // layout of the splitter is a horizontal layout. We fix this here
    if (s.fileVersion() == 0) {
        HorizontalSplitter = !HorizontalSplitter;
    }

    int Orientation = HorizontalSplitter ? Qt::Horizontal : Qt::Vertical;
    int WidgetCount = s.attributes().value("Count").toInt(&Ok);
    if (!Ok) {
        return false;
    }
    QX_DOCK_PRINT("Restore NodeSplitter Orientation: " << Orientation << " WidgetCount: " << WidgetCount);
    QSplitter *Splitter = nullptr;
    if (!Testing) {
        Splitter = newSplitter(static_cast<Qt::Orientation>(Orientation));
    }
    bool Visible = false;
    QList<int> Sizes;
    while (s.readNextStartElement()) {
        QWidget *ChildNode = nullptr;
        bool Result = true;
        if (s.name() == QLatin1String("Splitter")) {
            Result = restoreSplitter(s, ChildNode, Testing);
        } else if (s.name() == QLatin1String("Area")) {
            Result = restoreDockArea(s, ChildNode, Testing);
        } else if (s.name() == QLatin1String("Sizes")) {
            QString sSizes = s.readElementText().trimmed();
            QX_DOCK_PRINT("Sizes: " << sSizes);
            QTextStream TextStream(&sSizes);
            while (!TextStream.atEnd()) {
                int value;
                TextStream >> value;
                Sizes.append(value);
            }
        } else {
            s.skipCurrentElement();
        }

        if (!Result) {
            return false;
        }

        if (Testing || !ChildNode) {
            continue;
        }

        QX_DOCK_PRINT("ChildNode isVisible " << ChildNode->isVisible() << " isVisibleTo "
                                         << ChildNode->isVisibleTo(Splitter));
        Splitter->addWidget(ChildNode);
        Visible |= ChildNode->isVisibleTo(Splitter);
    }
    if (!Testing) {
        updateSplitterHandles(Splitter);
    }

    if (Sizes.count() != WidgetCount) {
        return false;
    }

    if (!Testing) {
        if (!Splitter->count()) {
            delete Splitter;
            Splitter = nullptr;
        } else {
            Splitter->setSizes(Sizes);
            Splitter->setVisible(Visible);
        }
        CreatedWidget = Splitter;
    } else {
        CreatedWidget = nullptr;
    }

    return true;
}

bool DockContainerWidgetPrivate::restoreDockArea(DockStateReader &s, QWidget *&CreatedWidget, bool Testing)
{
    DockAreaWidget *dockArea = nullptr;
    auto Result = DockAreaWidget::restoreState(s, dockArea, Testing, _this);
    if (Result && dockArea) {
        appendDockAreas({dockArea});
    }
    CreatedWidget = dockArea;
    return Result;
}

bool DockContainerWidgetPrivate::restoreSideBar(DockStateReader &s, QWidget *&CreatedWidget, bool Testing)
{
    Q_UNUSED(CreatedWidget)
    // Simply ignore side bar auto hide widgets from saved state if
    // auto hide support is disabled
    if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) {
        return true;
    }

    bool Ok;
    auto Area = (Qx::SideBarLocation)s.attributes().value("Area").toInt(&Ok);
    if (!Ok) {
        return false;
    }

    while (s.readNextStartElement()) {
        if (s.name() != QLatin1String("Widget")) {
            continue;
        }

        auto Name = s.attributes().value("Name");
        if (Name.isEmpty()) {
            return false;
        }

        bool Ok;
        bool Closed = s.attributes().value("Closed").toInt(&Ok);
        if (!Ok) {
            return false;
        }

        int Size = s.attributes().value("Size").toInt(&Ok);
        if (!Ok) {
            return false;
        }

        s.skipCurrentElement();
        DockWidget *dockWidget = dockManager->findDockWidget(Name.toString());
        if (!dockWidget || Testing) {
            continue;
        }

        auto SideBar = _this->sideTabBar(Area);
        DockAutoHideContainer *AutoHideContainer;
        if (dockWidget->isAutoHide()) {
            AutoHideContainer = dockWidget->autoHideDockContainer();
            if (AutoHideContainer->sideBar() != SideBar) {
                SideBar->addAutoHideWidget(AutoHideContainer);
            }
        } else {
            AutoHideContainer = SideBar->insertDockWidget(-1, dockWidget);
        }
        AutoHideContainer->setSize(Size);
        dockWidget->setProperty(internal::ClosedProperty, Closed);
        dockWidget->setProperty(internal::DirtyProperty, false);
    }

    return true;
}

bool DockContainerWidgetPrivate::restoreChildNodes(DockStateReader &s, QWidget *&CreatedWidget, bool Testing)
{
    bool Result = true;
    while (s.readNextStartElement()) {
        if (s.name() == QLatin1String("Splitter")) {
            Result = restoreSplitter(s, CreatedWidget, Testing);
            QX_DOCK_PRINT("Splitter");
        } else if (s.name() == QLatin1String("Area")) {
            Result = restoreDockArea(s, CreatedWidget, Testing);
            QX_DOCK_PRINT("DockAreaWidget");
        } else if (s.name() == QLatin1String("SideBar")) {
            Result = restoreSideBar(s, CreatedWidget, Testing);
            QX_DOCK_PRINT("SideBar");
        } else {
            s.skipCurrentElement();
            QX_DOCK_PRINT("Unknown element");
        }
    }

    return Result;
}

DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockwidget)
{
    DockAreaWidget *NewDockArea = new DockAreaWidget(dockManager, _this);
    NewDockArea->addDockWidget(dockwidget);
    addDockArea(NewDockArea, area);
    NewDockArea->updateTitleBarVisibility();
    LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea;
    return NewDockArea;
}

void DockContainerWidgetPrivate::addDockArea(DockAreaWidget *NewDockArea, DockWidgetArea area)
{
    auto InsertParam = internal::dockAreaInsertParameters(area);
    // As long as we have only one dock area in the splitter we can adjust
    // its orientation
    if (DockAreas.count() <= 1) {
        RootSplitter->setOrientation(InsertParam.orientation());
    }

    QSplitter *Splitter = RootSplitter;
    if (Splitter->orientation() == InsertParam.orientation()) {
        insertWidgetIntoSplitter(Splitter, NewDockArea, InsertParam.append());
        updateSplitterHandles(Splitter);
        if (Splitter->isHidden()) {
            Splitter->show();
        }
    } else {
        QSplitter *NewSplitter = newSplitter(InsertParam.orientation());
        if (InsertParam.append()) {
            QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter);
            NewSplitter->addWidget(Splitter);
            NewSplitter->addWidget(NewDockArea);
            updateSplitterHandles(NewSplitter);
            delete li;
        } else {
            NewSplitter->addWidget(NewDockArea);
            QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter);
            NewSplitter->addWidget(Splitter);
            updateSplitterHandles(NewSplitter);
            delete li;
        }
        RootSplitter = NewSplitter;
    }

    addDockAreasToList({NewDockArea});
}

void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget)
{
#if defined(QT_DEBUG)
    QSplitter *Splitter = qobject_cast<QSplitter *>(widget);
    QByteArray buf;
    buf.fill(' ', level * 4);
    if (Splitter) {
    #ifdef QX_DOCK_DEBUG_PRINT
        qDebug("%sSplitter %s v: %s c: %s", (const char *)buf, (Splitter->orientation() == Qt::Vertical) ? "--" : "|",
               Splitter->isHidden() ? " " : "v", QString::number(Splitter->count()).toStdString().c_str());
        std::cout << (const char *)buf << "Splitter " << ((Splitter->orientation() == Qt::Vertical) ? "--" : "|") << " "
                  << (Splitter->isHidden() ? " " : "v") << " " << QString::number(Splitter->count()).toStdString()
                  << std::endl;
    #endif
        for (int i = 0; i < Splitter->count(); ++i) {
            dumpRecursive(level + 1, Splitter->widget(i));
        }
    } else {
        DockAreaWidget *dockArea = qobject_cast<DockAreaWidget *>(widget);
        if (!dockArea) {
            return;
        }
    #ifdef QX_DOCK_DEBUG_PRINT
        qDebug("%sDockArea", (const char *)buf);
        std::cout << (const char *)buf << (dockArea->isHidden() ? " " : "v")
                  << (dockArea->openDockWidgetsCount() > 0 ? " " : "c") << " dockArea "
                  << "[hs: " << dockArea->sizePolicy().horizontalStretch()
                  << ", vs: " << dockArea->sizePolicy().verticalStretch() << "]" << std::endl;
        buf.fill(' ', (level + 1) * 4);
        for (int i = 0; i < dockArea->dockWidgetsCount(); ++i) {
            std::cout << (const char *)buf << (i == dockArea->currentIndex() ? "*" : " ");
            DockWidget *dockWidget = dockArea->dockWidget(i);
            std::cout << (dockWidget->isHidden() ? " " : "v");
            std::cout << (dockWidget->isClosed() ? "c" : " ") << " ";
            std::cout << dockWidget->windowTitle().toStdString() << std::endl;
        }
    #endif
    }
#else
    Q_UNUSED(level);
    Q_UNUSED(widget);
#endif
}

DockAreaWidget *DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, DockWidget *dockwidget,
                                                                     DockAreaWidget *TargetDockArea, int Index)
{
    if (CenterDockWidgetArea == area) {
        TargetDockArea->insertDockWidget(Index, dockwidget);
        TargetDockArea->updateTitleBarVisibility();
        return TargetDockArea;
    }

    DockAreaWidget *NewDockArea = new DockAreaWidget(dockManager, _this);
    NewDockArea->addDockWidget(dockwidget);
    auto InsertParam = internal::dockAreaInsertParameters(area);

    QSplitter *TargetAreaSplitter = internal::findParent<QSplitter *>(TargetDockArea);
    int index = TargetAreaSplitter->indexOf(TargetDockArea);
    if (TargetAreaSplitter->orientation() == InsertParam.orientation()) {
        QX_DOCK_PRINT("TargetAreaSplitter->orientation() == InsertParam.orientation()");
        TargetAreaSplitter->insertWidget(index + InsertParam.insertOffset(), NewDockArea);
        updateSplitterHandles(TargetAreaSplitter);
        // do nothing, if flag is not enabled
        if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) {
            adjustSplitterSizesOnInsertion(TargetAreaSplitter);
        }
    } else {
        QX_DOCK_PRINT("TargetAreaSplitter->orientation() != InsertParam.orientation()");
        auto TargetAreaSizes = TargetAreaSplitter->sizes();
        QSplitter *NewSplitter = newSplitter(InsertParam.orientation());
        NewSplitter->addWidget(TargetDockArea);

        insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append());
        updateSplitterHandles(NewSplitter);
        TargetAreaSplitter->insertWidget(index, NewSplitter);
        updateSplitterHandles(TargetAreaSplitter);
        if (DockManager::testConfigFlag(DockManager::EqualSplitOnInsertion)) {
            TargetAreaSplitter->setSizes(TargetAreaSizes);
            adjustSplitterSizesOnInsertion(NewSplitter);
        }
    }

    addDockAreasToList({NewDockArea});
    return NewDockArea;
}

DockContainerWidget::DockContainerWidget(DockManager *dockManager, QWidget *parent)
    : QFrame(parent), d(new DockContainerWidgetPrivate(this))
{
    d->dockManager = dockManager;
    d->isFloating = floatingWidget() != nullptr;

    d->Layout = new QGridLayout();
    d->Layout->setContentsMargins(0, 0, 0, 0);
    d->Layout->setSpacing(0);
    d->Layout->setColumnStretch(1, 1);
    d->Layout->setRowStretch(1, 1);
    setLayout(d->Layout);

    // The function d->newSplitter() accesses the config flags from dock
    // manager which in turn requires a properly constructed dock manager.
    // If this dock container is the dock manager, then it is not properly
    // constructed yet because this base class constructor is called before
    // the constructor of the dockManager private class
    if (dockManager != this) {
        d->dockManager->registerDockContainer(this);
        createRootSplitter();
        createSideTabBarWidgets();
    }
}

DockContainerWidget::~DockContainerWidget()
{
    if (d->dockManager) {
        d->dockManager->removeDockContainer(this);
    }

    delete d;
}

DockAreaWidget *DockContainerWidget::addDockWidget(DockWidgetArea area, DockWidget *dockwidget,
                                                     DockAreaWidget *dockAreaWidget, int Index)
{
    auto TopLevelDockWidget = topLevelDockWidget();
    DockAreaWidget *OldDockArea = dockwidget->dockAreaWidget();
    if (OldDockArea) {
        OldDockArea->removeDockWidget(dockwidget);
    }

    dockwidget->setDockManager(d->dockManager);
    DockAreaWidget *dockArea;
    if (dockAreaWidget) {
        dockArea = d->addDockWidgetToDockArea(area, dockwidget, dockAreaWidget, Index);
    } else {
        dockArea = d->addDockWidgetToContainer(area, dockwidget);
    }

    if (TopLevelDockWidget) {
        auto NewTopLevelDockWidget = topLevelDockWidget();
        // If the container contained only one visible dock widget, the we need
        // to emit a top level event for this widget because it is not the one and
        // only visible docked widget anymore
        if (!NewTopLevelDockWidget) {
            DockWidget::emitTopLevelEventForWidget(TopLevelDockWidget, false);
        }
    }
    return dockArea;
}

DockAutoHideContainer *DockContainerWidget::createAndSetupAutoHideContainer(SideBarLocation area,
                                                                              DockWidget *dockWidget)
{
    if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) {
        Q_ASSERT_X(false, "DockContainerWidget::createAndInitializeDockWidgetOverlayContainer",
                   "Requested area does not exist in config");
        return nullptr;
    }
    if (d->dockManager != dockWidget->dockManager()) {
        dockWidget->setDockManager(d->dockManager);   // Auto hide Dock Container needs a valid dock manager
    }

    return sideTabBar(area)->insertDockWidget(-1, dockWidget);
}

void DockContainerWidget::removeDockWidget(DockWidget *dockwidget)
{
    DockAreaWidget *Area = dockwidget->dockAreaWidget();
    if (Area) {
        Area->removeDockWidget(dockwidget);
    }
}

unsigned int DockContainerWidget::zOrderIndex() const
{
    return d->zOrderIndex;
}

bool DockContainerWidget::isInFrontOf(DockContainerWidget *Other) const
{
    return this->zOrderIndex() > Other->zOrderIndex();
}

bool DockContainerWidget::event(QEvent *e)
{
    bool Result = QWidget::event(e);
    if (e->type() == QEvent::WindowActivate) {
        d->zOrderIndex = ++zOrderCounter;
    } else if (e->type() == QEvent::Show && !d->zOrderIndex) {
        d->zOrderIndex = ++zOrderCounter;
    }

    return Result;
}

QList<DockAutoHideContainer *> DockContainerWidget::autoHideWidgets() const
{
    return d->AutoHideWidgets;
}

void DockContainerWidget::addDockArea(DockAreaWidget *dockAreaWidget, DockWidgetArea area)
{
    DockContainerWidget *Container = dockAreaWidget->dockContainer();
    if (Container && Container != this) {
        Container->removeDockArea(dockAreaWidget);
    }

    d->addDockArea(dockAreaWidget, area);
}

void DockContainerWidget::removeDockArea(DockAreaWidget *area)
{
    QX_DOCK_PRINT("DockContainerWidget::removeDockArea");
    // If it is an auto hide area, then there is nothing much to do
    if (area->isAutoHide()) {
        area->setAutoHideDockContainer(nullptr);
        return;
    }

    area->disconnect(this);
    d->DockAreas.removeAll(area);
    DockSplitter *Splitter = internal::findParent<DockSplitter *>(area);

    // Remove are from parent splitter and recursively hide tree of parent
    // splitters if it has no visible content
    area->setParent(nullptr);
    internal::hideEmptyParentSplitters(Splitter);

    // Remove this area from cached areas
    auto p = std::find(std::begin(d->LastAddedAreaCache), std::end(d->LastAddedAreaCache), area);
    if (p != std::end(d->LastAddedAreaCache)) {
        *p = nullptr;
    }

    // If splitter has more than 1 widgets, we are finished and can leave
    if (Splitter->count() > 1) {
        goto emitAndExit;
    }

    // If this is the RootSplitter we need to remove empty splitters to
    // avoid too many empty splitters
    if (Splitter == d->RootSplitter) {
        QX_DOCK_PRINT("Removed from RootSplitter");
        // If splitter is empty, we are finished
        if (!Splitter->count()) {
            Splitter->hide();
            goto emitAndExit;
        }

        QWidget *widget = Splitter->widget(0);
        QSplitter *ChildSplitter = qobject_cast<QSplitter *>(widget);
        // If the one and only content widget of the splitter is not a splitter
        // then we are finished
        if (!ChildSplitter) {
            goto emitAndExit;
        }

        // We replace the superfluous RootSplitter with the ChildSplitter
        ChildSplitter->setParent(nullptr);
        QLayoutItem *li = d->Layout->replaceWidget(Splitter, ChildSplitter);
        d->RootSplitter = ChildSplitter;
        delete li;
        QX_DOCK_PRINT("RootSplitter replaced by child splitter");
    } else if (Splitter->count() == 1) {
        QX_DOCK_PRINT("Replacing splitter with content");
        QSplitter *ParentSplitter = internal::findParent<QSplitter *>(Splitter);
        auto Sizes = ParentSplitter->sizes();
        QWidget *widget = Splitter->widget(0);
        widget->setParent(this);
        internal::replaceSplitterWidget(ParentSplitter, Splitter, widget);
        ParentSplitter->setSizes(Sizes);
    }

    delete Splitter;
    Splitter = nullptr;

emitAndExit:
    updateSplitterHandles(Splitter);
    DockWidget *TopLevelWidget = topLevelDockWidget();

    // Updated the title bar visibility of the dock widget if there is only
    // one single visible dock widget
    DockWidget::emitTopLevelEventForWidget(TopLevelWidget, true);
    dumpLayout();
    d->emitDockAreasRemoved();
}

DockAreaWidget *DockContainerWidget::dockAreaAt(const QPoint &GlobalPos) const
{
    for (const auto &dockArea : d->DockAreas) {
        if (dockArea->isVisible() && dockArea->rect().contains(dockArea->mapFromGlobal(GlobalPos))) {
            return dockArea;
        }
    }

    return nullptr;
}

DockAreaWidget *DockContainerWidget::dockArea(int Index) const
{
    return (Index < dockAreaCount()) ? d->DockAreas[Index] : nullptr;
}

bool DockContainerWidget::isFloating() const
{
    return d->isFloating;
}

int DockContainerWidget::dockAreaCount() const
{
    return d->DockAreas.count();
}

int DockContainerWidget::visibleDockAreaCount() const
{
    int Result = 0;
    for (auto dockArea : d->DockAreas) {
        Result += dockArea->isHidden() ? 0 : 1;
    }

    return Result;

    // TODO Cache or precalculate this to speed it up because it is used during
    // movement of floating widget
    // return d->visibleDockAreaCount();
}

void DockContainerWidget::dropFloatingWidget(DockFloatingContainer *FloatingWidget, const QPoint &TargetPos)
{
    QX_DOCK_PRINT("DockContainerWidget::dropFloatingWidget");
    DockWidget *SingleDroppedDockWidget = FloatingWidget->topLevelDockWidget();
    DockWidget *SingleDockWidget = topLevelDockWidget();
    DockAreaWidget *dockArea = dockAreaAt(TargetPos);
    auto dropArea = InvalidDockWidgetArea;
    auto ContainerDropArea = d->dockManager->containerOverlay()->dropAreaUnderCursor();
    bool Dropped = false;

    if (dockArea) {
        auto dropOverlay = d->dockManager->dockAreaOverlay();
        dropOverlay->setAllowedAreas(dockArea->allowedAreas());
        dropArea = dropOverlay->showOverlay(dockArea);
        if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) {
            dropArea = InvalidDockWidgetArea;
        }

        if (dropArea != InvalidDockWidgetArea) {
            QX_DOCK_PRINT("Dock Area Drop Content: " << dropArea);
            d->dropIntoSection(FloatingWidget, dockArea, dropArea);
            Dropped = true;
        }
    }

    // mouse is over container
    if (InvalidDockWidgetArea == dropArea) {
        dropArea = ContainerDropArea;
        QX_DOCK_PRINT("Container Drop Content: " << dropArea);
        if (dropArea != InvalidDockWidgetArea) {
            d->dropIntoContainer(FloatingWidget, dropArea);
            Dropped = true;
        }
    }

    // Remove the auto hide widgets from the FloatingWidget and insert
    // them into this widget
    for (auto AutohideWidget : FloatingWidget->dockContainer()->autoHideWidgets()) {
        auto SideBar = sideTabBar(AutohideWidget->sideBarLocation());
        SideBar->addAutoHideWidget(AutohideWidget);
    }

    if (Dropped) {
        // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351
        FloatingWidget->hideAndDeleteLater();

        // If we dropped a floating widget with only one single dock widget, then we
        // drop a top level widget that changes from floating to docked now
        DockWidget::emitTopLevelEventForWidget(SingleDroppedDockWidget, false);

        // If there was a top level widget before the drop, then it is not top
        // level widget anymore
        DockWidget::emitTopLevelEventForWidget(SingleDockWidget, false);
    }

    window()->activateWindow();
    if (SingleDroppedDockWidget) {
        d->dockManager->notifyWidgetOrAreaRelocation(SingleDroppedDockWidget);
    }
    d->dockManager->notifyFloatingWidgetDrop(FloatingWidget);
}

void DockContainerWidget::dropWidget(QWidget *Widget, DockWidgetArea DropArea, DockAreaWidget *TargetAreaWidget)
{
    DockWidget *SingleDockWidget = topLevelDockWidget();
    if (TargetAreaWidget) {
        d->moveToNewSection(Widget, TargetAreaWidget, DropArea);
    } else {
        d->moveToContainer(Widget, DropArea);
    }

    // If there was a top level widget before the drop, then it is not top
    // level widget anymore
    DockWidget::emitTopLevelEventForWidget(SingleDockWidget, false);

    window()->activateWindow();
    d->dockManager->notifyWidgetOrAreaRelocation(Widget);
}

QList<DockAreaWidget *> DockContainerWidget::openedDockAreas() const
{
    QList<DockAreaWidget *> Result;
    for (auto dockArea : d->DockAreas) {
        if (!dockArea->isHidden()) {
            Result.append(dockArea);
        }
    }

    return Result;
}

QList<DockWidget *> DockContainerWidget::openedDockWidgets() const
{
    QList<DockWidget *> DockWidgetList;
    for (auto dockArea : d->DockAreas) {
        if (!dockArea->isHidden()) {
            DockWidgetList.append(dockArea->openedDockWidgets());
        }
    }

    return DockWidgetList;
}

bool DockContainerWidget::hasOpenDockAreas() const
{
    for (auto dockArea : d->DockAreas) {
        if (!dockArea->isHidden()) {
            return true;
        }
    }

    return false;
}

void DockContainerWidget::saveState(QXmlStreamWriter &s) const
{
    QX_DOCK_PRINT("DockContainerWidget::saveState isFloating " << isFloating());

    s.writeStartElement("Container");
    s.writeAttribute("Floating", QString::number(isFloating() ? 1 : 0));
    if (isFloating()) {
        DockFloatingContainer *FloatingWidget = floatingWidget();
        QByteArray Geometry = FloatingWidget->saveGeometry();
#if QT_VERSION < 0x050900
        s.writeTextElement("Geometry", qByteArrayToHex(Geometry, ' '));
#else
        s.writeTextElement("Geometry", Geometry.toHex(' '));
#endif
    }
    d->saveChildNodesState(s, d->RootSplitter);
    d->saveAutoHideWidgetsState(s);
    s.writeEndElement();
}

bool DockContainerWidget::restoreState(DockStateReader &s, bool Testing)
{
    bool IsFloating = s.attributes().value("Floating").toInt();
    QX_DOCK_PRINT("Restore DockContainerWidget Floating" << IsFloating);

    QWidget *NewRootSplitter{};
    if (!Testing) {
        d->VisibleDockAreaCount = -1;   // invalidate the dock area count
        d->DockAreas.clear();
        std::fill(std::begin(d->LastAddedAreaCache), std::end(d->LastAddedAreaCache), nullptr);
    }

    if (IsFloating) {
        QX_DOCK_PRINT("Restore floating widget");
        if (!s.readNextStartElement() || s.name() != QLatin1String("Geometry")) {
            return false;
        }

        QByteArray GeometryString = s.readElementText(DockStateReader::ErrorOnUnexpectedElement).toLocal8Bit();
        QByteArray Geometry = QByteArray::fromHex(GeometryString);
        if (Geometry.isEmpty()) {
            return false;
        }

        if (!Testing) {
            DockFloatingContainer *FloatingWidget = floatingWidget();
            if (FloatingWidget) {
                FloatingWidget->restoreGeometry(Geometry);
            }
        }
    }

    if (!d->restoreChildNodes(s, NewRootSplitter, Testing)) {
        return false;
    }

    if (Testing) {
        return true;
    }

    // If the root splitter is empty, rostoreChildNodes returns a 0 pointer
    // and we need to create a new empty root splitter
    if (!NewRootSplitter) {
        NewRootSplitter = d->newSplitter(Qt::Horizontal);
    }

    d->Layout->replaceWidget(d->RootSplitter, NewRootSplitter);
    QSplitter *OldRoot = d->RootSplitter;
    d->RootSplitter = qobject_cast<QSplitter *>(NewRootSplitter);
    OldRoot->deleteLater();

    return true;
}

QSplitter *DockContainerWidget::rootSplitter() const
{
    return d->RootSplitter;
}

void DockContainerWidget::createRootSplitter()
{
    if (d->RootSplitter) {
        return;
    }
    d->RootSplitter = d->newSplitter(Qt::Horizontal);
    d->Layout->addWidget(d->RootSplitter, 1,
                         1);   // Add it to the center - the 0 and 2 indexes are used for the SideTabBar widgets
}

void DockContainerWidget::createSideTabBarWidgets()
{
    if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideFeatureEnabled)) {
        return;
    }

    {
        auto Area = SideBarLocation::SideBarLeft;
        d->SideTabBarWidgets[Area] = new DockAutoHideSideBar(this, Area);
        d->Layout->addWidget(d->SideTabBarWidgets[Area], 1, 0);
    }

    {
        auto Area = SideBarLocation::SideBarRight;
        d->SideTabBarWidgets[Area] = new DockAutoHideSideBar(this, Area);
        d->Layout->addWidget(d->SideTabBarWidgets[Area], 1, 2);
    }

    {
        auto Area = SideBarLocation::SideBarBottom;
        d->SideTabBarWidgets[Area] = new DockAutoHideSideBar(this, Area);
        d->Layout->addWidget(d->SideTabBarWidgets[Area], 2, 1);
    }

    {
        auto Area = SideBarLocation::SideBarTop;
        d->SideTabBarWidgets[Area] = new DockAutoHideSideBar(this, Area);
        d->Layout->addWidget(d->SideTabBarWidgets[Area], 0, 1);
    }
}

void DockContainerWidget::dumpLayout()
{
#if (QX_DOCK_DEBUG_LEVEL > 0)
    qDebug("\n\nDumping layout --------------------------");
    std::cout << "\n\nDumping layout --------------------------" << std::endl;
    d->dumpRecursive(0, d->RootSplitter);
    qDebug("--------------------------\n\n");
    std::cout << "--------------------------\n\n" << std::endl;
#endif
}

DockAreaWidget *DockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const
{
    return d->LastAddedAreaCache[areaIdToIndex(area)];
}

bool DockContainerWidget::hasTopLevelDockWidget() const
{
    auto DockAreas = openedDockAreas();
    if (DockAreas.count() != 1) {
        return false;
    }

    return DockAreas[0]->openDockWidgetsCount() == 1;
}

DockWidget *DockContainerWidget::topLevelDockWidget() const
{
    auto TopLevelDockArea = topLevelDockArea();
    if (!TopLevelDockArea) {
        return nullptr;
    }

    auto DockWidgets = TopLevelDockArea->openedDockWidgets();
    if (DockWidgets.count() != 1) {
        return nullptr;
    }

    return DockWidgets[0];
}

DockAreaWidget *DockContainerWidget::topLevelDockArea() const
{
    auto DockAreas = openedDockAreas();
    if (DockAreas.count() != 1) {
        return nullptr;
    }

    return DockAreas[0];
}

QList<DockWidget *> DockContainerWidget::dockWidgets() const
{
    QList<DockWidget *> Result;
    for (const auto dockArea : d->DockAreas) {
        Result.append(dockArea->dockWidgets());
    }

    return Result;
}

void DockContainerWidget::updateSplitterHandles(QSplitter *splitter)
{
    d->updateSplitterHandles(splitter);
}

void DockContainerWidget::registerAutoHideWidget(DockAutoHideContainer *AutohideWidget)
{
    d->AutoHideWidgets.append(AutohideWidget);
    Q_EMIT autoHideWidgetCreated(AutohideWidget);
    QX_DOCK_PRINT("d->AutoHideWidgets.count() " << d->AutoHideWidgets.count());
}

void DockContainerWidget::removeAutoHideWidget(DockAutoHideContainer *AutohideWidget)
{
    d->AutoHideWidgets.removeAll(AutohideWidget);
}

DockWidget::DockWidgetFeatures DockContainerWidget::features() const
{
    DockWidget::DockWidgetFeatures Features(DockWidget::AllDockWidgetFeatures);
    for (const auto dockArea : d->DockAreas) {
        Features &= dockArea->features();
    }

    return Features;
}

DockFloatingContainer *DockContainerWidget::floatingWidget() const
{
    return internal::findParent<DockFloatingContainer *>(this);
}

void DockContainerWidget::closeOtherAreas(DockAreaWidget *KeepOpenArea)
{
    for (const auto dockArea : d->DockAreas) {
        if (dockArea == KeepOpenArea) {
            continue;
        }

        if (!dockArea->features(BitwiseAnd).testFlag(DockWidget::DockWidgetClosable)) {
            continue;
        }

        // We do not close areas with widgets with custom close handling
        if (dockArea->features(BitwiseOr).testFlag(DockWidget::CustomCloseHandling)) {
            continue;
        }

        dockArea->closeArea();
    }
}

DockAutoHideSideBar *DockContainerWidget::sideTabBar(SideBarLocation area) const
{
    return d->SideTabBarWidgets[area];
}

QRect DockContainerWidget::contentRect() const
{
    if (!d->RootSplitter) {
        return QRect();
    }

    return d->RootSplitter->geometry();
}

QRect DockContainerWidget::contentRectGlobal() const
{
    if (!d->RootSplitter) {
        return QRect();
    }
    return internal::globalGeometry(d->RootSplitter);
}

DockManager *DockContainerWidget::dockManager() const
{
    return d->dockManager;
}

void DockContainerWidget::handleAutoHideWidgetEvent(QEvent *e, QWidget *w)
{
    if (!DockManager::testAutoHideConfigFlag(DockManager::AutoHideShowOnMouseOver)) {
        return;
    }

    if (dockManager()->isRestoringState()) {
        return;
    }

    auto AutoHideTab = qobject_cast<DockAutoHideTab *>(w);
    if (AutoHideTab) {
        switch (e->type()) {
        case QEvent::Enter:
            if (!AutoHideTab->dockWidget()->isVisible()) {
                d->DelayedAutoHideTab = AutoHideTab;
                d->DelayedAutoHideShow = true;
                d->DelayedAutoHideTimer.start();
            } else {
                d->DelayedAutoHideTimer.stop();
            }
            break;

        case QEvent::MouseButtonPress:
            d->DelayedAutoHideTimer.stop();
            break;

        case QEvent::Leave:
            if (AutoHideTab->dockWidget()->isVisible()) {
                d->DelayedAutoHideTab = AutoHideTab;
                d->DelayedAutoHideShow = false;
                d->DelayedAutoHideTimer.start();
            } else {
                d->DelayedAutoHideTimer.stop();
            }
            break;

        default:
            break;
        }
        return;
    }

    auto AutoHideContainer = qobject_cast<DockAutoHideContainer *>(w);
    if (AutoHideContainer) {
        switch (e->type()) {
        case QEvent::Enter:
        case QEvent::Hide:
            d->DelayedAutoHideTimer.stop();
            break;

        case QEvent::Leave:
            if (AutoHideContainer->isVisible()) {
                d->DelayedAutoHideTab = AutoHideContainer->autoHideTab();
                d->DelayedAutoHideShow = false;
                d->DelayedAutoHideTimer.start();
            }
            break;

        default:
            break;
        }
        return;
        return;
    }
}

QX_END_NAMESPACE
